import Vue from 'vue'
import axios from 'axios'
import store from '@/store'
import router from '@/router'
import notification from 'ant-design-vue/es/notification'
import message from 'ant-design-vue/es/message'
import { isJwtTokenExpire, isJwtTokenNearlyExpire } from './jwt'
import { refreshToken } from '@/api/login'
import { VueAxios } from './axios'
import { BASE_URL, DOWNLOAD_URL, LOGIN_PAGE, ACCESS_TOKEN, REFRESH_TOKEN_REQUEST, REQUEST_WHITE_LIST } from '@/config/constants'

// 全局标记
let requestingLogout = false
let refreshTokenPromise = null

/**
 * 创建 axios 实例
 */
const service = axios.create({
  baseURL: BASE_URL, // api base_url
  timeout: 10000, // 请求超时时间
  headers: {
    'Content-Type': 'application/json;charset=UTF-8'
  }
})

function isDownloadUrl (url) {
  const reqUrl = url.replace(BASE_URL, '')
  return DOWNLOAD_URL.includes(reqUrl)
}

function containDownloadHeader(response) {
  return !!response.headers['content-disposition']
}

/**
 * 退出登录，并根据所在页面做跳转
 * @param reject 当前请求的reject
 * @param reason 退出登录的原因
 */
function logout (reject, reason) {
  if (!requestingLogout) {
    requestingLogout = true
    store.dispatch('Logout')
      .then(() => {
        requestingLogout = false
        notification.warn({
          message: '请重新登录',
          description: reason
        })
        if (LOGIN_PAGE !== location.pathname) {
          router.push({ path: LOGIN_PAGE, query: { redirect: location.pathname } })
        }
        return reject(new Error(reason))
      })
  }
}

/**
 * 创建刷新token的Promise
 */
function createRefreshTokenPromise () {
  return new Promise((resolve, reject) => {
    refreshToken()
      .then(res => {
        // 如果刷新成功，响应头中会有ACCESS_TOKEN，在响应拦截器中会将token存入LocalStorage
        return resolve(res)
      })
      .catch(() => {
        const info = '刷新登录状态失败'
        notification.warn({
          message: '请重新登录',
          description: info
        })
        if (LOGIN_PAGE !== location.pathname) {
          router.push({ path: LOGIN_PAGE, query: { redirect: location.pathname } })
        }
        return reject(new Error(info))
      })
  })
}

/**
 * 将当前请求追加到刷新token的请求执行成功后执行
 * @param resolve 当前请求的resolve
 * @param reject 当前请求的reject
 * @param config 当前请求的config
 */
function appendRequestAfterRefreshToken (resolve, reject, config) {
  if (refreshTokenPromise) {
    refreshTokenPromise
      .then(() => {
        // 刷新token成功后，取消标记
        // 这里看起来像“自己删除自己”，
        // 但是这里只是把promise对象指向null，不会有问题
        refreshTokenPromise = null
        return resolve(config)
      })
      .catch(() => {
        return reject(new Error('刷新登录状态失败'))
      })
  }
}

/**
 * 请求拦截器
 */
service.interceptors.request.use(
  config => {
    console.debug('%s 拦截请求', config.url)

    const token = Vue.ls.get(ACCESS_TOKEN)
    const isExpire = isJwtTokenExpire(token)
    const isNearlyExpire = isJwtTokenNearlyExpire(token)
    const isWhiteListRequest = REQUEST_WHITE_LIST.includes(config.url)
    const isRefreshTokenRequest = REFRESH_TOKEN_REQUEST === config.url

    if (token && !isExpire) {
      config.headers['Authorization'] = `Bearer ${token}`
    } else {
      delete config.headers['Authorization']
    }

    return new Promise((resolve, reject) => {
      if (isWhiteListRequest) {
        // 白名单无需token，直接放行
        return resolve(config)
      } else if (token) {
        if (isNearlyExpire) {
          // 刷新token
          if (isRefreshTokenRequest) {
            // 如果请求就是刷新token的请求，直接放行
            return resolve(config)
            // 其他请求则刷新后放行
          } else {
            if (refreshTokenPromise) {
              // 如果正在刷新，那么就把本次请求追加到刷新token的请求后执行
              appendRequestAfterRefreshToken(resolve, reject, config)
            } else {
              refreshTokenPromise = createRefreshTokenPromise()
              // 如果刷新token成功就放行
              appendRequestAfterRefreshToken(resolve, reject, config)
            }
          }
        } else if (isExpire) {
          logout(reject, '登录已过期', config.url)
        } else {
          // token没过期，放行
          return resolve(config)
        }
      } else {
        logout(reject, '找不到访问凭证', config.url)
      }
    })
  },

  error => {
    // 发送请求发生的错误，仅记录一下
    console.error('发送请求出错', error)
  })

/**
 * 响应拦截器
 */
service.interceptors.response.use(
  response => {
    console.debug('%s 拦截响应 --> %s', response.config.url, JSON.stringify(response.data))

    // 如果后端返回响应头ACCESS_TOKEN或access_token，通过vue-ls库存到LocalStorage中
    const token = response.headers[ACCESS_TOKEN.toLowerCase()] || response.headers[ACCESS_TOKEN]
    if (token) {
      Vue.ls.set(ACCESS_TOKEN, token)
    }
    if (containDownloadHeader(response)) {
      return response
    } else {
      if (!response.data.succ) {
        message.error(response.data.msg)
        return Promise.reject(new Error(response.data.msg))
      }
      return response.data
    }
  },

  error => {
    console.error('响应错误, %o', error)
    if (error.response) {
      const data = error.response.data
      const description = data.msg
      const token = Vue.ls.get(ACCESS_TOKEN)
      if (error.response.status === 500) {
        message.error(description)
      }
      if (error.response.status === 403) {
        notification.error({
          message: '没有访问权限',
          description
        })
      }
      if (error.response.status === 401) {
        notification.error({
          message: '禁止访问',
          description
        })
        if (token) {
          store.dispatch('logout').then(() => {
            setTimeout(() => {
              window.location.reload()
            }, 1500)
          })
        }
      }
    }
    return Promise.reject(error)
  })

/**
 * 按Vue插件方式安装
 */
const installer = {
  vm: {},
  install (Vue) {
    Vue.use(VueAxios, service)
  }
}

/**
 * 导出
 */
export {
  installer as VueAxios,
  service as request
}
